# app/services/schedule_service.py:

from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
from croniter import croniter
import uuid
from datetime import datetime
import pytz
# <<< MODIFIED: Import ThreadPoolExecutor >>>
import concurrent.futures
# <<< END MODIFIED >>>

from app.core.config import settings
from app.db.session import SessionLocal
from app.crud.crud_job import crud_job
from app.crud.crud_schedule import crud_schedule
from app.db.models import JobStatus, JobType
# <<< MODIFIED: Import job_service at the top level if no circular dependency, or handle differently >>>
# Import it here for now, assuming direct call is still needed to submit
from app.services.job_service import job_service
# <<< END MODIFIED >>>


# 创建调度器
jobstores = {
    'default': SQLAlchemyJobStore(url=settings.DATABASE_URL)
}
scheduler = BackgroundScheduler(jobstores=jobstores)

# <<< MODIFIED: Create a ThreadPoolExecutor >>>
# 可以根据需要调整 max_workers
# 注意：这个执行器将在应用的整个生命周期内运行。确保在应用关闭时正确关闭它。
# 对于更复杂的应用，建议使用更健壮的后台任务系统如 Celery 或 RQ。
executor = concurrent.futures.ThreadPoolExecutor(max_workers=settings.SCHEDULE_WORKERS or 5)
# <<< END MODIFIED >>>


class ScheduleService:
    def __init__(self):
        self.scheduler = scheduler
        # 确保在应用启动时只启动一次调度器
        if not self.scheduler.running:
             self.scheduler.start()
             print("APScheduler started.") # 添加日志或打印以确认启动

    def validate_cron(self, cron_expression: str) -> bool:
        """验证CRON表达式是否有效"""
        try:
            croniter(cron_expression)
            return True
        except ValueError as e:
            raise ValueError(f"无效的CRON表达式: {str(e)}")

    def calculate_next_run(self, cron_expression: str) -> datetime:
        """计算下次运行时间"""
        try:
            # 使用 UTC 或配置的时区以避免歧义
            now_aware = datetime.now(pytz.timezone(settings.TIMEZONE))
            cron = croniter(cron_expression, now_aware)
            return cron.get_next(datetime)
        except Exception as e:
            raise ValueError(f"计算下次运行时间失败: {str(e)}")

    def add_job(self, schedule):
        """添加定时任务到调度器"""
        # from app.services.job_service import job_service # Moved import to top

        def run_spider_task_wrapper():
            """
            这个函数由 APScheduler 直接调用。
            它负责创建数据库记录，并提交实际的爬虫任务到线程池。
            """
            db = None # Initialize db to None
            try:
                db = SessionLocal()
                # 注意：这里需要重新获取 schedule 对象，因为原始的 schedule 对象可能已过时
                current_schedule = crud_schedule.get(db, id=schedule.id)
                if not current_schedule or not current_schedule.enabled:
                    print(f"Schedule {schedule.id} not found or disabled. Skipping run.")
                    return

                next_run_time = self.calculate_next_run(current_schedule.cron_expression)
                # 更新上次运行时间和下次运行时间
                crud_schedule.update(
                    db,
                    db_obj=current_schedule,
                    obj_in={
                        "last_run_at": datetime.now(pytz.timezone(settings.TIMEZONE)),
                        "next_run_at": next_run_time
                    }
                )

                # 创建新的爬虫运行任务记录
                job_data = {
                    "id": str(uuid.uuid4()),
                    "spider_id": current_schedule.spider_id,
                    "status": JobStatus.QUEUED,
                    "job_type": JobType.SPIDER_RUN,
                    "created_at": datetime.now(pytz.timezone(settings.TIMEZONE))
                    # 'schedule_id': current_schedule.id # 可以考虑添加关联
                }
                new_job = crud_job.create(db, obj_in=job_data)
                db.commit() # 提交事务以确保 job 记录被创建

                job_id_to_run = new_job.id
                spider_id_to_run = current_schedule.spider_id

                # <<< MODIFIED: Submit the actual job execution to the thread pool >>>
                print(f"Submitting job {job_id_to_run} for spider {spider_id_to_run} to executor.")
                # job_service.run_spider_job 是阻塞的，我们在线程池中运行它
                future = executor.submit(job_service.run_spider_job, spider_id_to_run, job_id_to_run)

                # 可以选择性地添加回调来处理任务完成或失败的情况
                # future.add_done_callback(handle_job_completion)
                # <<< END MODIFIED >>>

            except Exception as e:
                 print(f"Error in run_spider_task_wrapper for schedule {schedule.id}: {e}")
                 # Consider logging the error more formally
            finally:
                if db:
                    db.close()

        # 添加包装后的任务到调度器
        try:
            self.scheduler.add_job(
                run_spider_task_wrapper, # 使用包装函数
                CronTrigger.from_crontab(schedule.cron_expression, timezone=settings.TIMEZONE),
                id=f"schedule_{schedule.id}",
                name=f"Spider {schedule.spider_id} Schedule",
                replace_existing=True,
                misfire_grace_time=settings.MISFIRE_GRACE_TIME or 60 # 允许一定的任务延迟执行时间
            )
            print(f"Successfully added/updated job schedule_{schedule.id} to scheduler.")
        except Exception as e:
            print(f"Failed to add job schedule_{schedule.id} to scheduler: {e}")


    def update_job(self, schedule):
        """更新调度器中的任务"""
        print(f"Attempting to update schedule {schedule.id} in scheduler.")
        # 注意：APScheduler 的 add_job 设置了 replace_existing=True,
        # 所以理论上直接调用 add_job 就可以实现更新。
        # 但为了明确，可以先尝试移除再添加，或者直接调用 add_job。
        # self.remove_job(schedule.id) # 如果 replace_existing=True，这步可能不需要
        self.add_job(schedule)

    def remove_job(self, schedule_id: str):
        """从调度器中移除任务"""
        job_id = f"schedule_{schedule_id}"
        try:
            self.scheduler.remove_job(job_id)
            print(f"Successfully removed job {job_id} from scheduler.")
        except Exception as e: # 更具体可以是 apscheduler.jobstores.base.JobLookupError
            print(f"Job {job_id} not found in scheduler or could not be removed: {e}")
            pass # 如果任务不存在，忽略错误

    def init_schedules(self):
        """初始化所有启用的定时任务"""
        print("Initializing schedules...")
        db = None
        try:
            db = SessionLocal()
            active_schedules = crud_schedule.get_active_schedules(db)
            print(f"Found {len(active_schedules)} active schedules to load.")
            for schedule in active_schedules:
                self.add_job(schedule)
        except Exception as e:
            print(f"Error initializing schedules: {e}")
        finally:
            if db:
                db.close()

    # <<< MODIFIED: Add shutdown method for executor >>>
    def shutdown(self):
        """Gracefully shutdown the scheduler and executor"""
        print("Shutting down scheduler...")
        if self.scheduler.running:
            self.scheduler.shutdown()
        print("Shutting down executor...")
        executor.shutdown(wait=True)
        print("Shutdown complete.")
    # <<< END MODIFIED >>>


schedule_service = ScheduleService()

# <<< MODIFIED: Ensure graceful shutdown on application exit >>>
import atexit
atexit.register(schedule_service.shutdown)
# For frameworks like FastAPI, integrate shutdown into the application's lifecycle events.
# <<< END MODIFIED >>>